package com.sql.mysql.sharding.plugin;

import java.util.List;
import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandlerRegistry;

import com.sql.mysql.sharding.annotation.ShardingKeyObject;
import com.sql.mysql.sharding.annotation.ShardingKeyType;
import com.sql.mysql.sharding.threadlocal.ShardingThreadLocal;

import lombok.extern.slf4j.Slf4j;

//并不是真的要出发代理方法,类似StatementHandlerInterceptor的做法
// http://www.bijishequ.com/detail/95063
@Intercepts(value = {})
@Slf4j
public class ShardParameterHandlerInterceptor implements Interceptor {

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
		if (target instanceof ParameterHandler) {
			log.debug("进入ParameterHandlerInterceptor");
			// 开始遍历
			ShardingKeyObject shardingKey = ShardingThreadLocal.ShardingKeyObjectThreadLocal.get();
			log.debug(""+shardingKey.getAnnotationType());
			if (shardingKey.getAnnotationType() == ShardingKeyType.PARAMETER_INDEX) {
				// 如果是参数,则之前就已经赋值完毕了
				log.debug("ShardingKeyType.PARAMETER ,value is ok ,just return");
				return target;// 直接返回就好了
			}
			// 不做代理,只是取需要的数据
			DefaultParameterHandler DefaultParameterHandler = (DefaultParameterHandler) target;
			//
			// 以下代码直接
			// 从org.apache.ibatis.scripting.defaults.DefaultParameterHandler.setParameters拷贝而来
			// 不能直接从parameterObject取值
			Object parameterObject = DefaultParameterHandler.getParameterObject();
			Configuration configuration = ShardingThreadLocal.MappedStatementThreadLocal.get().getConfiguration();
			TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
			// BoundSql不好拿,先用反射拿,会不会慢?
			MetaObject metaObject = SystemMetaObject.forObject(DefaultParameterHandler);
			BoundSql boundSql = (BoundSql) metaObject.getValue("boundSql");

			log.debug("分片键 {}", shardingKey);
			List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
			if (parameterMappings != null) {
				for (int i = 0; i < parameterMappings.size(); i++) {
					ParameterMapping parameterMapping = parameterMappings.get(i);
					if (parameterMapping.getMode() != ParameterMode.OUT) {
						// 如果不是我要的shardingKey,就直接 continue
						String propertyName = parameterMapping.getProperty();
						if (false == propertyName.equals(shardingKey.getFiedName())) {
							log.debug("property {} ignore", propertyName);
							continue;
						}
						log.debug("found the property !");
						Object value;
						if (boundSql.hasAdditionalParameter(propertyName)) { // issue
																				// #448
																				// ask
																				// first
																				// for
																				// additional
																				// params
							value = boundSql.getAdditionalParameter(propertyName);
						} else if (parameterObject == null) {
							value = null;
						} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
							value = parameterObject;
						} else {
							metaObject = configuration.newMetaObject(parameterObject);
							value = metaObject.getValue(propertyName);
						}
						JdbcType jdbcType = parameterMapping.getJdbcType();
						if (value == null && jdbcType == null) {
							jdbcType = configuration.getJdbcTypeForNull();
						}
						// 再保存
						shardingKey.setValue(value);
						shardingKey.setJdbcType(jdbcType);
						shardingKey.setParameterMapping(parameterMapping);
						log.debug("sharding key -> {}", shardingKey);
						// 立刻返回
						return target;
					}
				}
			}
		}
		return target;
	}

	@Override
	public void setProperties(Properties properties) {
		log.debug("properties {}", properties);
	}

}
